package com.matrix.auth.service.impl;

import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.matrix.api.user.UserApi;
import com.matrix.api.user.dto.PasswordLoginDTO;
import com.matrix.api.user.dto.SmsLoginDTO;
import com.matrix.api.user.dto.SmsMfaLoginDTO;
import com.matrix.api.user.dto.UserRegisterDTO;
import com.matrix.api.user.vo.AccessTokenVO;
import com.matrix.api.user.vo.LoginSuccessVO;
import com.matrix.api.user.vo.PasswordVerifySuccessVO;
import com.matrix.api.user.vo.UserLoginInfoVO;
import com.matrix.auth.config.JwtProperties;
import com.matrix.auth.service.AuthService;
import com.matrix.auth.service.SmsService;
import com.matrix.auth.utils.JwtUtil;
import com.matrix.common.constant.RedisConstants;
import com.matrix.common.domain.Result;
import com.matrix.common.enums.ErrorCode;
import com.matrix.common.enums.SmsTypeEnum;
import com.matrix.common.enums.UserTypeEnum;
import com.matrix.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.rpc.RpcException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author 有点甜
 * @since 2025/7/25
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {

    private final SmsService smsService;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;
    private final JwtProperties jwtProperties;
    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper objectMapper;

    @DubboReference(check = false)
    private UserApi userApi;

    @Override
    public void sendVerificationCode(String phone, SmsTypeEnum type) {
        smsService.sendCode(phone, type);
    }

    @Override
    public Result<Void> register(UserRegisterDTO registerDTO) {
        smsService.verifyCode(registerDTO.getPhone(), SmsTypeEnum.REGISTER, registerDTO.getVerificationCode());

        try {
            userApi.register(registerDTO);
            return Result.success();
        } catch (RpcException e) {
            throw new BusinessException("注册服务暂时不可用,请稍后再试");
        }
    }

    @Override
    public Result<?> passwordLogin(PasswordLoginDTO loginDTO) {
        String account = loginDTO.getAccount();
        String lockKey = RedisConstants.buildKey(RedisConstants.LOGIN_LOCK_KEY, account);
        String failCountKey = RedisConstants.buildKey(RedisConstants.LOGIN_FAIL_COUNT_KEY, account);

        if (redisTemplate.hasKey(lockKey)) {
            throw new BusinessException("密码错误次数过多,请15分钟后再试");
        }

        UserLoginInfoVO userInfo;
        try {
            userInfo = userApi.findByAccount(account, loginDTO.getUserType());
        } catch (RpcException e) {
            throw new BusinessException("用户服务暂不可用,请稍后重试");
        }

        if (userInfo == null) {
            handleLoginFailure(failCountKey, lockKey);
            throw new BusinessException("用户名或密码错误");
        }

        if (!passwordEncoder.matches(loginDTO.getPassword(), userInfo.getPassword())) {
            handleLoginFailure(failCountKey, lockKey);
            throw new BusinessException("用户名或密码错误");
        }

        redisTemplate.delete(failCountKey);

        if (userInfo.getStatus() != 1) {
            throw new BusinessException("账户已被禁用");
        }

        if (loginDTO.getUserType() == UserTypeEnum.MERCHANT) {
            String tempToken = UUID.randomUUID().toString().replace("-", "");
            String tempTokenKey = RedisConstants.buildKey("login:mfa:token:%s", tempToken);

            try {
                String userInfoJson = objectMapper.writeValueAsString(userInfo);
                redisTemplate.opsForValue().set(tempTokenKey, userInfoJson, 5, TimeUnit.MINUTES);
            } catch (JsonProcessingException e) {
                log.error("Failed to serialize userInfo for MFA login", e);
                throw new BusinessException("系统繁忙,请稍后重试");
            }

            sendVerificationCode(userInfo.getAccount(), SmsTypeEnum.LOGIN);

            PasswordVerifySuccessVO vo = PasswordVerifySuccessVO.builder()
                    .message("密码验证成功,请输入短信验证码完成登录")
                    .phone(DesensitizedUtil.mobilePhone(userInfo.getAccount()))
                    .tempToken(tempToken)
                    .build();
            return Result.success(vo);
        } else {
            redisTemplate.delete(failCountKey);
            return buildLoginSuccessResult(userInfo);
        }
    }

    @Override
    public Result<LoginSuccessVO> smsMfaLogin(SmsMfaLoginDTO mfaLoginDTO) {
        String tempTokenKey = RedisConstants.buildKey("login:mfa:token:%s", mfaLoginDTO.getTempToken());
        String userInfoJson = redisTemplate.opsForValue().get(tempTokenKey);

        if (!StringUtils.hasText(userInfoJson)) {
            throw new BusinessException("凭证已过期或无效,请重新登录");
        }

        UserLoginInfoVO userInfo;
        try {
            userInfo = objectMapper.readValue(userInfoJson, UserLoginInfoVO.class);
        } catch (JsonProcessingException e) {
            log.error("Failed to deserialize userInfo for MFA login", e);
            throw new BusinessException("系统繁忙,请稍后重试");
        }

        smsService.verifyCode(userInfo.getAccount(), SmsTypeEnum.LOGIN, mfaLoginDTO.getCode());

        redisTemplate.delete(tempTokenKey);

        return buildLoginSuccessResult(userInfo);
    }

    @Override
    public Result<LoginSuccessVO> smsLogin(SmsLoginDTO loginDTO) {
        smsService.verifyCode(loginDTO.getPhone(), SmsTypeEnum.LOGIN, loginDTO.getCode());

        UserLoginInfoVO userInfo;
        try {
            userInfo = userApi.findByAccount(loginDTO.getPhone(), loginDTO.getUserType());
        } catch (RpcException e) {
            throw new BusinessException("用户服务暂不可用,请稍后重试");
        }

        if (userInfo == null) {
            throw new BusinessException("该手机号未注册");
        }

        if (userInfo.getStatus() != 1) {
            throw new BusinessException("账户已被禁用");
        }

        return buildLoginSuccessResult(userInfo);
    }

    @Override
    public Result<AccessTokenVO> refreshAccessToken(String refreshToken) {
        if (jwtUtil.isTokenExpired(refreshToken, jwtProperties.getRefreshSecret())) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED);
        }

        Long userId;
        String userType;
        try {
            userId = jwtUtil.getUserIdFromRefreshToken(refreshToken);
            userType = jwtUtil.getUserTypeFromRefreshToken(refreshToken);
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED);
        }

        String refreshTokenKey = RedisConstants.buildKey(RedisConstants.USER_REFRESH_TOKEN_KEY, userId);
        String storedRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey);

        if (storedRefreshToken == null || !storedRefreshToken.equals(refreshToken)) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED);
        }

        String newAccessToken = jwtUtil.generateAccessToken(userId, userType);

        AccessTokenVO vo = AccessTokenVO.builder()
                .accessToken(newAccessToken)
                .build();

        return Result.success(vo);
    }

    private void handleLoginFailure(String failCountKey, String lockKey) {
        Long failCount = redisTemplate.opsForValue().increment(failCountKey);

        if (failCount != null && failCount == 1) {
            redisTemplate.expire(failCountKey, 15, TimeUnit.MINUTES);
        }

        if (failCount != null && failCount >= 5) {
            redisTemplate.opsForValue().set(lockKey, "1", 15, TimeUnit.MINUTES);
            redisTemplate.delete(failCountKey);
        }
    }

    private Result<LoginSuccessVO> buildLoginSuccessResult(UserLoginInfoVO userInfo) {
        Map<String, String> tokenPair = jwtUtil.generateTokenPair(userInfo.getUserId(), userInfo.getUserType());
        String accessToken = tokenPair.get("accessToken");
        String refreshToken = tokenPair.get("refreshToken");

        String refreshTokenKey = RedisConstants.buildKey(RedisConstants.USER_REFRESH_TOKEN_KEY, userInfo.getUserId());
        redisTemplate.opsForValue().set(refreshTokenKey, refreshToken, jwtProperties.getRefreshTtl(), TimeUnit.MILLISECONDS);

        LoginSuccessVO vo = LoginSuccessVO.builder()
                .userId(userInfo.getUserId())
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();

        return Result.success(vo);
    }
}
